// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright @ Huawei Technologies Co., Ltd. 2022-2022. ALL rights reversed.
 */
#ifdef CONFIG_EULEROS_KERNEL_FAULT_NOTIFIER
#include <linux/smp.h>
#include <linux/percpu.h>
#include <linux/kernel_fault_notifier.h>
#include <linux/printk.h>
#include <linux/kexec.h>

static ATOMIC_NOTIFIER_HEAD(kernel_fault_notify_chain);
static int kernel_fault_notify_chain_nest_count;

void notify_kernel_fault(enum tagKERNEL_FAULT_TYPES_E type)
{
	const int cur_cpu = smp_processor_id();

	pr_notice("kernel fault(0x%x) notification starting on CPU %d\n", type, cur_cpu);
#ifdef CONFIG_X86_64
	wbinvd();
#endif
	wmb(); /* drain writebuffer */

	atomic_notifier_call_chain(&kernel_fault_notify_chain, type, NULL);

	pr_notice("kernel fault(0x%x) notification finished on CPU %d\n", type, cur_cpu);
#ifdef CONFIG_X86_64
	wbinvd();
#endif
	wmb(); /* drain writebuffer */
}

int register_kernel_fault_notifier(struct notifier_block *nb)
{
	if (nb == NULL) {
		pr_err("register kernel fault notification failed. nb is NULL\n");
		return -EINVAL;
	}

	if (nb->notifier_call == NULL) {
		pr_err("register kernel fault notification failed. notifier_call is NULL\n");
		return -EINVAL;
	}

	pr_notice("register kernel fault notification. nb: %pK, nb_call: %pS\n",
			nb, nb->notifier_call);

	return atomic_notifier_chain_register(&kernel_fault_notify_chain, nb);
}
EXPORT_SYMBOL_GPL(register_kernel_fault_notifier);

int unregister_kernel_fault_notifier(struct notifier_block *nb)
{
	if (nb == NULL) {
		pr_err("unregister kernel fault notification failed. nb is NULL\n");
		return -EINVAL;
	}

	pr_notice("unregister kernel fault notification. nb: %pK, nb_call: %pS\n",
			nb, nb->notifier_call);

	return atomic_notifier_chain_unregister(&kernel_fault_notify_chain, nb);
}
EXPORT_SYMBOL_GPL(unregister_kernel_fault_notifier);

void do_notify_kernel_fault(void)
{
	/* avoid nest loop if panic happens when notify kernel fault callbacks */
	kernel_fault_notify_chain_nest_count++;
	if (kernel_fault_notify_chain_nest_count <= 1) {
		notify_kernel_fault(KERNEL_FAULT_PANIC);
		kernel_fault_notify_chain_nest_count--;
	} else {
		pr_emerg("Nest loop in %s\n", __func__);
		__crash_kexec(NULL);
	}
}
#endif
